/*
 * Copyright 2015 Victor Albertos
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package io.rx_cache.internal.encrypt;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.security.SecureRandom;

import javax.crypto.Cipher;
import javax.crypto.CipherInputStream;
import javax.crypto.CipherOutputStream;
import javax.crypto.KeyGenerator;
import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;

/**
 * Encrypt/Decrypt the file data
 */
public final class BuiltInEncryptor implements Encryptor {
  private final static String CODE_MODE = "AES/GCM/NoPadding";
  private static final int KEY_LENGTH = 128;

      // Max 128 bits by default. See http://stackoverflow.com/a/24907555/5502014
  private static final int FILE_BUF = 1024;
  private Cipher encryptCipher;
  private Cipher decryptCipher;

  @Override public void encrypt(String key, File decryptedFile, File encryptedFile) {
    initCiphers(key);

    try {
      CipherInputStream cis =
          new CipherInputStream(new FileInputStream(decryptedFile), encryptCipher);
      write(cis, new FileOutputStream(encryptedFile));
    } catch (Exception e) {
      e.printStackTrace();
    }
  }

  @Override public void decrypt(String key, File encryptedFile, File decryptedFile) {
    initCiphers(key);

    try {
      CipherOutputStream cos =
          new CipherOutputStream(new FileOutputStream(decryptedFile), decryptCipher);
      write(new FileInputStream(encryptedFile), cos);
    } catch (Exception e) {
      e.printStackTrace();
    }
  }

  private void initCiphers(String key) {
    try {
      SecretKeySpec secretKey = generateSecretKey(key);

      encryptCipher = Cipher.getInstance(CODE_MODE);
      encryptCipher.init(Cipher.ENCRYPT_MODE, secretKey);

      decryptCipher = Cipher.getInstance(CODE_MODE);
      decryptCipher.init(Cipher.DECRYPT_MODE, secretKey);
    } catch (Exception e) {
      e.printStackTrace();
    }
  }

  private SecretKeySpec generateSecretKey(String key) throws Exception {
    SecureRandom secureRandom = SecureRandom.getInstance("SHA1PRNG");
    secureRandom.setSeed(key.getBytes("UTF-8"));
    KeyGenerator keyGenerator = KeyGenerator.getInstance(CODE_MODE);
    keyGenerator.init(KEY_LENGTH, secureRandom);
    SecretKey secretKey = keyGenerator.generateKey();

    return new SecretKeySpec(secretKey.getEncoded(), CODE_MODE);
  }

  private void write(InputStream is, OutputStream os) {
    byte[] bytes = new byte[FILE_BUF];
    int numBytes;

    try {
      while ((numBytes = is.read(bytes)) != -1) {
        os.write(bytes, 0, numBytes);
      }
    } catch (IOException e) {
      e.printStackTrace();
    } finally {
      try {
        is.close();
        os.flush();
        os.close();
      } catch (IOException ex) {
        ex.printStackTrace();
      }
    }
  }
}
